import { isClient } from "@vueuse/core";
import { castArray as ensureArray } from "lodash-es";

import type { MentionCtx, MentionOption } from "./types";
export const isFirefox = (): boolean =>
  isClient && /firefox/i.test(window.navigator.userAgent);

export const filterOption = (
  pattern: string,
  option: MentionOption
): boolean => {
  const lowerCase = pattern.toLowerCase();
  const label = option.label || option.value;
  return label.toLowerCase().includes(lowerCase);
};

export const getSelectionEnd = (
  inputEl: HTMLDivElement | HTMLInputElement | HTMLTextAreaElement
): number | null => {
  let selectionEnd: number | null = null;

  if ("selectionEnd" in inputEl) {
    // input 或 textarea
    selectionEnd = inputEl.selectionEnd;
  } else {
    // contenteditable div
    const selection = window.getSelection();
    if (selection && selection.rangeCount > 0) {
      const range = selection.getRangeAt(0);
      const preCaretRange = range.cloneRange();
      preCaretRange.selectNodeContents(inputEl);
      preCaretRange.setEnd(range.endContainer, range.endOffset);
      selectionEnd = preCaretRange.toString().length;
    }
  }
  return selectionEnd;
};

export const getSelectionStart = (
  inputEl: HTMLDivElement | HTMLInputElement | HTMLTextAreaElement
): number | null => {
  let selectionStart: number | null = null;

  if ("selectionStart" in inputEl) {
    // input 或 textarea
    selectionStart = inputEl.selectionStart;
  } else {
    // contenteditable div
    const selection = window.getSelection();
    if (selection && selection.rangeCount > 0) {
      const range = selection.getRangeAt(0);
      const preCaretRange = range.cloneRange();
      preCaretRange.selectNodeContents(inputEl);
      preCaretRange.setEnd(range.startContainer, range.endOffset);
      selectionStart = preCaretRange.toString().length;
    }
  }
  return selectionStart;
};

export const getTextContent = (
  inputEl: HTMLDivElement | HTMLInputElement | HTMLTextAreaElement
): string | null => {
  let text: string | null = null;

  if ("value" in inputEl) {
    // input 或 textarea
    text = inputEl.value;
  } else {
    text = inputEl.textContent;
  }

  return text;
};

export const getInnerHTML = (
  inputEl: HTMLDivElement | HTMLInputElement | HTMLTextAreaElement
): string | null => {
  let text: string | null = null;

  if ("value" in inputEl) {
    // input 或 textarea
    text = inputEl.value;
  } else {
    text = inputEl.innerHTML;
  }

  return text;
};
export const getMentionCtx = (
  inputEl: HTMLDivElement | HTMLInputElement | HTMLTextAreaElement,
  prefix: string | string[],
  split: string
) => {
  const selectionEnd: number | null = getSelectionEnd(inputEl);
  if (selectionEnd === null) return;

  const inputValue = getTextContent(inputEl) || "";
  const prefixArray = ensureArray(prefix);

  let splitIndex = -1;
  let mentionCtx: MentionCtx | undefined;

  for (let i = selectionEnd; i >= 0; --i) {
    const char = inputValue[i];
    if (char === split || char === "\n" || char === "\r") {
      splitIndex = i;
      continue;
    }
    if (prefixArray.includes(char)) {
      const end = splitIndex === -1 ? selectionEnd : splitIndex;
      const pattern = inputValue.slice(i + 1, end);
      mentionCtx = {
        pattern,
        start: i + 1,
        end,
        prefix: char,
        prefixIndex: i,
        splitIndex,
        selectionEnd,
      };
      break;
    }
  }

  if (mentionCtx) {
    destoryHiddenNodeAll(inputEl);
    insertHiddenNode();
  }

  return mentionCtx;
};

/**
 * 获取可编辑区域中光标的屏幕坐标
 * @param {HTMLElement} editableElement - 可编辑的div元素
 * @returns {Object} 包含x和y坐标的对象，失败时返回null
 */
export const getCaretScreenCoordinates = (
  editableElement: HTMLDivElement | HTMLInputElement | HTMLTextAreaElement
) => {
  // 检查元素是否存在且可编辑
  if (
    !editableElement ||
    !editableElement.contentEditable ||
    editableElement.contentEditable !== "true"
  ) {
    console.error("请提供一个contenteditable='true'的div元素");
    return { x: 0, y: 0 };
  }

  let selection, range, rect, clientX, clientY;

  // 处理现代浏览器
  if (window.getSelection) {
    selection = window.getSelection();
    if (!selection) {
      return { x: 0, y: 0 };
    }
    // 检查是否有选中的内容或光标
    if (selection.rangeCount === 0) {
      console.warn("当前没有选中的内容或光标");
      return { x: 0, y: 0 };
    }

    range = selection.getRangeAt(0);
    range.collapse(true); // 将范围折叠到起始位置（光标位置）

    // 创建一个虚拟元素来标记光标位置
    const caretMarker = document.createElement("span");
    caretMarker.style.position = "absolute";
    caretMarker.style.width = "1px";
    caretMarker.style.height = "1px";
    caretMarker.style.visibility = "hidden";
    range.insertNode(caretMarker);

    // 获取标记元素的位置
    rect = caretMarker.getBoundingClientRect();
    clientX = rect.left;
    clientY = rect.top;

    // 清理虚拟元素
    caretMarker?.parentNode?.removeChild(caretMarker);
  } else {
    console.error("不支持的浏览器，无法获取光标位置");
    return { x: 0, y: 0 };
  }

  // 将客户端坐标转换为屏幕坐标
  const scrollX = window.pageXOffset || document.documentElement.scrollLeft;
  const scrollY = window.pageYOffset || document.documentElement.scrollTop;
  const screenX = clientX + scrollX;
  const screenY = clientY + scrollY;

  return { x: screenX, y: screenY };
};
/**
 * fork from textarea-caret-position
 * https://github.com/component/textarea-caret-position
 * The MIT License (MIT)
 * Copyright (c) 2015 Jonathan Ong me@jongleberry.com
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
export const getCursorPosition = (
  element: HTMLDivElement | HTMLInputElement | HTMLTextAreaElement,
  options = {
    debug: false,
    useSelectionEnd: false,
  }
) => {
  const computed = window.getComputedStyle(element);
  if (element.nodeName === "DIV") {
    const coordinate = getCaretScreenCoordinates(element);
    return {
      top: coordinate.y,
      left: coordinate.x,
      // We don't use line-height since it may be too large for position. Eg. 34px
      // for input
      height: Number.parseInt(computed.fontSize as string) * 1.5,
    };
  }

  const selectionStart = getSelectionStart(element) || 0;
  const selectionEnd = getSelectionEnd(element) || 0;
  const position = options.useSelectionEnd ? selectionEnd : selectionStart;
  // We'll copy the properties below into the mirror div.
  // Note that some browsers, such as Firefox, do not concatenate properties
  // into their shorthand (e.g. padding-top, padding-bottom etc. -> padding),
  // so we have to list every single property explicitly.
  const properties: string[] = [
    "direction", // RTL support
    "boxSizing",
    "width", // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
    "height",
    "overflowX",
    "overflowY", // copy the scrollbar for IE
    "borderTopWidth",
    "borderRightWidth",
    "borderBottomWidth",
    "borderLeftWidth",
    "borderStyle",
    "paddingTop",
    "paddingRight",
    "paddingBottom",
    "paddingLeft",
    // https://developer.mozilla.org/en-US/docs/Web/CSS/font
    "fontStyle",
    "fontVariant",
    "fontWeight",
    "fontStretch",
    "fontSize",
    "fontSizeAdjust",
    "lineHeight",
    "fontFamily",
    "textAlign",
    "textTransform",
    "textIndent",
    "textDecoration", // might not make a difference, but better be safe
    "letterSpacing",
    "wordSpacing",
    "tabSize",
    "MozTabSize",
  ];

  if (options.debug) {
    const el = document.querySelector(
      "#input-textarea-caret-position-mirror-div"
    );
    if (el?.parentNode) el.parentNode.removeChild(el);
  }

  // The mirror div will replicate the textareas style
  const div = document.createElement("div");
  div.id = "input-textarea-caret-position-mirror-div";
  document.body.appendChild(div);

  const style = div.style;

  const isInput = element.nodeName === "INPUT";

  // Default textarea styles
  style.whiteSpace = isInput ? "nowrap" : "pre-wrap";
  if (!isInput) style.wordWrap = "break-word"; // only for textarea-s

  // Position off-screen
  style.position = "absolute"; // required to return coordinates properly
  if (!options.debug) style.visibility = "hidden"; // not 'display: none' because we want rendering

  // Transfer the element's properties to the div
  properties.forEach((prop) => {
    if (isInput && prop === "lineHeight") {
      // Special case for <input>s because text is rendered centered and line height may be != height
      if (computed.boxSizing === "border-box") {
        const height = Number.parseInt(computed.height as string);
        const outerHeight =
          Number.parseInt(computed.paddingTop as string) +
          Number.parseInt(computed.paddingBottom as string) +
          Number.parseInt(computed.borderTopWidth as string) +
          Number.parseInt(computed.borderBottomWidth as string);
        const targetHeight =
          outerHeight + Number.parseInt(computed.lineHeight as string);
        if (height > targetHeight) {
          style.lineHeight = `${height - outerHeight}px`;
        } else if (height === targetHeight) {
          style.lineHeight = computed.lineHeight;
        } else {
          style.lineHeight = "0";
        }
      } else {
        style.lineHeight = computed.height;
      }
    } else {
      style[prop as any] = computed[prop as any];
    }
  });

  if (isFirefox()) {
    // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
    if (element.scrollHeight > Number.parseInt(computed.height as string)) {
      style.overflowY = "scroll";
    }
  } else {
    style.overflow = "hidden"; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
  }

  const textContent = getTextContent(element) || "";
  div.textContent = textContent.slice(0, Math.max(0, position));
  // The second special handling for input type="text" vs textarea:
  // spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
  if (isInput && div.textContent) {
    div.textContent = div.textContent.replace(/\s/g, "\u00A0");
  }

  const span = document.createElement("span");
  // Wrapping must be replicated *exactly*, including when a long word gets
  // onto the next line, with whitespace at the end of the line before (#7).
  // The  *only* reliable way to do that is to copy the *entire* rest of the
  // textareas content into the <span> created at the caret position.
  // For inputs, just '.' would be enough, but no need to bother.
  span.textContent = textContent.slice(Math.max(0, position)) || "."; // || because a completely empty faux span doesn't render at all
  span.style.position = "relative";
  span.style.left = `${-element.scrollLeft}px`;
  span.style.top = `${-element.scrollTop}px`;
  div.appendChild(span);

  const relativePosition = {
    top: span.offsetTop + Number.parseInt(computed.borderTopWidth as string),
    left: span.offsetLeft + Number.parseInt(computed.borderLeftWidth as string),
    // We don't use line-height since it may be too large for position. Eg. 34px
    // for input
    height: Number.parseInt(computed.fontSize as string) * 1.5,
  };

  if (options.debug) {
    span.style.backgroundColor = "#aaa";
  } else {
    document.body.removeChild(div);
  }

  if (relativePosition.left >= element.clientWidth) {
    relativePosition.left = element.clientWidth;
  }
  return relativePosition;
};
export const setCaretPosition = (el: HTMLElement, pos: number) => {
  if (window.getSelection) {
    // For modern browsers
    const selection = window.getSelection();
    const range = document.createRange();
    const textNode = el.firstChild;

    if (textNode && textNode.nodeType === Node.TEXT_NODE) {
      range.setStart(textNode, pos);
      range.collapse(true);
    } else {
      range.setStart(el, pos);
      range.collapse(true);
    }

    selection?.removeAllRanges();
    selection?.addRange(range);
  }
};

export const insertHiddenNode = () => {
  // 创建隐藏节点
  const hiddenNode = document.createElement("span");
  hiddenNode.textContent = "";
  hiddenNode.className = "custom-marker";

  // 获取当前光标位置
  const selection = window.getSelection();
  if (!selection) {
    return;
  }

  if (selection.rangeCount > 0) {
    const range = selection.getRangeAt(0);

    // 在光标位置插入隐藏节点
    range.insertNode(hiddenNode);

    // 恢复光标位置到插入节点后
    range.setStartAfter(hiddenNode);
    range.collapse(true);

    selection.removeAllRanges();
    selection.addRange(range);
  }
};

export const destoryHiddenNodeAll = (
  element: HTMLDivElement | HTMLInputElement | HTMLTextAreaElement
) => {
  const markers = element.querySelectorAll(".custom-marker");
  markers.forEach((marker) => marker.remove());
};

export const getPositionInParent = (element: any) => {
  const parent = element.offsetParent || element.parentElement;
  const elementRect = element.getBoundingClientRect();
  const parentRect = parent.getBoundingClientRect();

  // 获取父元素的 transform 矩阵
  const transform = window.getComputedStyle(parent).transform;
  let matrix = new DOMMatrix(transform);

  // 计算相对坐标（考虑 transform 和滚动）
  const x = (elementRect.left - parentRect.left) / matrix.a + parent.scrollLeft;
  const y = (elementRect.top - parentRect.top) / matrix.d + parent.scrollTop;

  return { x, y };
};
